package com.guli.gulimall.product.service.impl;

import com.guli.gulimall.product.vo.CategoryLevel2Vo;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guli.common.utils.PageUtils;
import com.guli.common.utils.Query;

import com.guli.gulimall.product.dao.CategoryDao;
import com.guli.gulimall.product.entity.CategoryEntity;
import com.guli.gulimall.product.service.CategoryService;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private CategoryDao categoryDao;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );
        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> treeList() {
        List<CategoryEntity> treeList = (List<CategoryEntity>) redisTemplate.opsForHash().get("map", "treeList");
        if (treeList==null){
            //查到所有列表数据
            List<CategoryEntity> all = categoryDao.selectList(null);
            //利用递归加兰达姆表达式完成三级树
            treeList = getTreeList(all, 0L);
            redisTemplate.opsForHash().put("map","treeList",treeList);
        }
        return treeList;
    }

    private List<CategoryEntity> getTreeList(List<CategoryEntity> all,Long catId){
        List<CategoryEntity> collect = all.stream().filter(item -> item.getParentCid().equals(catId)).map(item -> {
            item.setCategoryEntities(getTreeList(all,item.getCatId()));
            return item;
        }).sorted((item1,item2)->(item1.getSort()==null?0:item1.getSort())-(item2.getSort()==null?0:item2.getSort())).collect(Collectors.toList());
        return collect;
    }

    @Override
    public List<CategoryEntity> selectL1List() {
        List<CategoryEntity> list=null;
        list = (List<CategoryEntity>) redisTemplate.opsForHash().get("map", "oneTreeList");
        if (list==null){
            list=baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid",0).orderByAsc("sort"));
            redisTemplate.opsForHash().put("map", "oneTreeList",list);
        }
        return list;
    }

    @Override
    public Map<String, List<CategoryLevel2Vo>> findCatalog() {
        //数据库优化（只查询一次） 减少与数据库交互只查询一次拿到全部
        List<CategoryEntity> categories = baseMapper.selectList(null);
        //查询二级树封装成map集合-->进行map收集一级树的catId为key CategoryLevel2Vo为value
        Map<String, List<CategoryLevel2Vo>> twoTreeList = categories.stream().filter(item -> item.getParentCid().equals(0L)).map(item -> {
            return item;
        }).collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {
            //查询出第一个一级树的二级树集合
            List<CategoryLevel2Vo> level2 = categories.stream().filter(item -> item.getParentCid().equals(value.getCatId()))
                .sorted((v1,v2)->(v1.getSort()==null?0:v1.getSort())-(v2.getSort()==null?0:v2.getSort())).map(l2 -> {
                    CategoryLevel2Vo categoryLevel2Vo = new CategoryLevel2Vo();
                    categoryLevel2Vo.setId(l2.getCatId());
                    categoryLevel2Vo.setName(l2.getName());
                    categoryLevel2Vo.setParentCid(l2.getParentCid());
                    //查询出第一个二级树的三级树集合
                    List<CategoryLevel2Vo.Catalog3List> leve3 = categories.stream().filter(l3 -> l3.getParentCid().equals(l2.getCatId()))
                        .sorted((v1,v2)->(v1.getSort()==null?0:v1.getSort())-(v2.getSort()==null?0:v2.getSort())).map(l3 -> {
                            CategoryLevel2Vo.Catalog3List catalog3List = new CategoryLevel2Vo.Catalog3List();
                            catalog3List.setId(l3.getCatId());
                            catalog3List.setName(l3.getName());
                            catalog3List.setParentCid(l3.getParentCid());
                            return catalog3List;
                        }).collect(Collectors.toList());
                    categoryLevel2Vo.setCatalog3List(leve3);
                    return categoryLevel2Vo;
                }).collect(Collectors.toList());
            return level2;
            }
        ));
        return twoTreeList;
    }

//    @Override
//    public Map<String, List<CategoryLevel2Vo>> findCatalogCache() {
//        //缓存优化
//        //不为空直接返回
//        String twoTreeListJson = (String) redisTemplate.opsForValue().get("twoTreeList");
//        if (twoTreeListJson==null){
//            String uuid = UUID.randomUUID().toString().replace("-", "");
//            try {
//                //为空时进行分布式锁的优化 占坑（高并发环境）
//                //先加锁进行绑定 value用uuid标识为个人锁(避免错误删除)  （加锁+过期 原子性操作） 设置锁的过期时间（足够长）避免死锁或业务逻辑出现异常时无法主动删锁所造成的死锁
//                Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid,30, TimeUnit.SECONDS);
//                //判断占锁成功-->业务逻辑
//                if (flag){
//                    System.out.println("查数据库");
//                    Map<String, List<CategoryLevel2Vo>> twoTreeList=findCatalog();
//                    //添加随机过期时间避免缓存雪崩
//                    redisTemplate.opsForValue().set("twoTreeList",JSONObject.toJSONString(twoTreeList),300+new Random().nextInt(300),TimeUnit.SECONDS);
//                    return twoTreeList;
//                }else {
//                    //占锁不成功->进行休眠 回旋
//                    try {
//                        Thread.sleep(100);
//                        findCatalogCache();
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                }
//            } finally {
//                //不论结果如何，都要删除锁（避免死锁）
//                //对比删除原子性 对比是本人的锁并且删除（原子性操作避免并发请求同时执行该操作）redis+lua脚本
//                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//                redisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class),Arrays.asList("lock"),uuid);
//            }
//        }
//        System.out.println("缓存命中");
//        return JSONObject.parseObject(twoTreeListJson,new TypeReference<Map<String, List<CategoryLevel2Vo>>>(){});
//    }

    @Override
    public Map<String, List<CategoryLevel2Vo>> findCatalogCache(){
        //解决高并发之缓存优化
        //缓冲难点：
        // 1.缓存穿透 持续访问一个空值，不走缓存
        // 2.缓存雪崩 多个缓存的数据在同一时间过期，造成集体访问数据库（数据库过载）（设置随机数的过期时间）
        // 3.缓存击穿 并发时 多个请求同时访问数据库 （加锁（原始的分布式锁））

        Map<String, List<CategoryLevel2Vo>> twoTreeList=null;
        //缓存不为空时直接返回
        twoTreeList= (Map<String, List<CategoryLevel2Vo>>) redisTemplate.opsForHash().get("map", "twoTreeList");
        //缓存为空走缓存逻辑
        if (twoTreeList==null){
            //设置锁 锁+过期时间（原子性操作）
            String uuid = UUID.randomUUID().toString().replace("-", "");
            try {
                Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
                //某个请求占锁成功执行业务逻辑
                if (flag) {
                    System.out.println("查数据库。。。。");
                    twoTreeList=findCatalog();
                    redisTemplate.opsForHash().put("map","twoTreeList",twoTreeList);
                    redisTemplate.expire("map",300+new Random().nextInt(300),TimeUnit.SECONDS);
                }else {
                    //占锁失败 进行睡眠回旋
                    try {
                        Thread.sleep(100);
                        return findCatalogCache();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }finally {//锁一定要去掉避免死锁
                //判断uuid+删除锁（原子性操作避免并发请求同时执行该操作）redis+lua脚本
                String script="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                redisTemplate.execute(new DefaultRedisScript(script,Long.class),Arrays.asList("lock"),uuid);
            }
        }
        return twoTreeList;
    }

    @Override
    public Map<String, List<CategoryLevel2Vo>> findCatalogRedissonLockCache() {
        //获取读写锁
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        Map<String, List<CategoryLevel2Vo>> twoTreeList=null;
        try {
            rwLock.readLock().lock();//加读锁
            //查询redis
            twoTreeList= (Map<String, List<CategoryLevel2Vo>>) redisTemplate.opsForHash().get("map", "twoTreeList");
        } finally {
            rwLock.readLock().unlock();//解读锁
        }
        //为空 缓存不命中 查数据库
        if (twoTreeList==null){
            try {
                //加写锁
                rwLock.writeLock().lock();
                //占坑成功后再次判断是否前一个占坑的线程是否已经存入缓存
                twoTreeList= (Map<String, List<CategoryLevel2Vo>>) redisTemplate.opsForHash().get("map", "twoTreeList");
                //有缓存直接返回
                if (twoTreeList!=null) {System.out.println("缓存命中"); return twoTreeList;}
                System.out.println("查询数据库");
                twoTreeList=findCatalog();
                redisTemplate.opsForHash().put("map","twoTreeList",twoTreeList);
                //不相同的过期时间避免缓存雪崩
                redisTemplate.expire("map",300+new Random().nextInt(300),TimeUnit.SECONDS);
                return twoTreeList;
            } finally {
                //解写锁
                rwLock.writeLock().unlock();
            }
        }
        //不为空 缓存命中返回
        System.out.println("缓存命中");
        return twoTreeList;
    }


}